#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifdef MELIS_OS
#include <finsh.h>
#endif

#include "mpp_camera.h"
#include "mpp_audio.h"
#include "lindsink.h"
#include "osal_types.h"
#include "osal_log.h"
#include "osal_common.h"

#define SAVE_FILE 0
#if SAVE_FILE
FILE * g_fd = 0;
char * g_filepath = "/tmp/1.h264";
FILE * g_fd_audio = 0;
char * g_filepath_audio = "/tmp/1.audio";
#endif

typedef struct _sample_mpp2livesink_ctx_t
{
    _handle_t           h_camera;
    _handle_t           h_audio;

    int                 b_enable_preview;
    camera_config_t     camera_cfg;
    preview_config_t    preview_cfg;
    venc_config_t       venc_cfg;

    mpp_audio_config_t  audio_cfg;
    mpp_aenc_config_t   aenc_cfg;

    lindsink_t *        p_sink;
    lindsink_config_t   sink_cfg;
} sample_mpp2livesink_ctx_t;

static int lindaudio_cb(void *user, int msg, void * ext1, void * ext2)
{
    sample_mpp2livesink_ctx_t * p_ctx = (sample_mpp2livesink_ctx_t *)user;
    switch (msg)
    {
        case LD_AUDIO_MSG_AENC:
        {
            logv("message: LD_AUDIO_MSG_AENC");
            static int aenc_cnt = 0;
            int index = *(int*)ext2;
            av_frame2_t * p_frame = (av_frame2_t *)ext1;
            // p_frame->pts = get_tick_us();
            logv("aenc_cnt: %d, index: %d, cb aenc data: %p, size: %d, pts: %lld",
                aenc_cnt++, index, p_frame->data[0], p_frame->size[0], p_frame->pts);
            p_ctx->p_sink->write(p_ctx->p_sink, p_frame);
#if SAVE_FILE
            fwrite(p_frame->data[0], p_frame->size[0], 1, g_fd_audio);
#endif
            break;
        }
        default:
            loge("lindaudio_cb unknown msg type: %d", msg);
            break;
    }

    return 0;
}

static int lindcamera_cb(void *user, int msg, void * ext1, void * ext2)
{
    sample_mpp2livesink_ctx_t * p_ctx = (sample_mpp2livesink_ctx_t *)user;
    switch (msg)
    {
        case LD_CAMERA_MSG_CB_ES:
        {
            logv("message: LD_CAMERA_MSG_CB_ES");
            static int venc_cnt = 0;
            int index = *(int*)ext2;
            av_frame2_t * p_frame = (av_frame2_t *)ext1;
            // p_frame->pts = get_tick_us();
            logv("venc_cnt: %d, index: %d, cb es data: %p, size: %d, pts: %lld",
                venc_cnt++, index, p_frame->data[0], p_frame->size[0], p_frame->pts);
            p_ctx->p_sink->write(p_ctx->p_sink, p_frame);
#if SAVE_FILE
            fwrite(p_frame->data[0], p_frame->size[0], 1, g_fd);
#endif
            break;
        }
        default:
            loge("lindcamera_cb unknown msg type: %d", msg);
            break;
    }

    return 0;
}

static int init_mpp_source(sample_mpp2livesink_ctx_t * p_ctx)
{
    int ret = 0;

    /**
     * create camera
     */
    if (p_ctx->sink_cfg.have_video)
    {
        p_ctx->h_camera = mpp_cam_create(0);
        if (p_ctx->h_camera == (_handle_t)NULL)
        {
            loge("mpp_cam_create failed");
            return -1;
        }
        mpp_cam_set_params(p_ctx->h_camera, &p_ctx->camera_cfg);
    }

    /**
     * create audio source
     */
    if (p_ctx->sink_cfg.have_audio)
    {
        p_ctx->h_audio = mpp_audio_create(0, &p_ctx->audio_cfg);
        if (p_ctx->h_audio == (_handle_t)NULL)
        {
            loge("mpp_audio_create failed");
            return -1;
        }
    }

    return 0;
}

static void deinit_mpp_source(sample_mpp2livesink_ctx_t * p_ctx)
{
    /**
     * destroy audio
     */
    if (p_ctx->h_audio != 0)
    {
        mpp_audio_destroy(p_ctx->h_audio);
        p_ctx->h_audio = 0;
    }

    /**
     * destroy camera
     */
    if (p_ctx->h_camera != 0)
    {
        mpp_cam_destroy(p_ctx->h_camera);
        p_ctx->h_camera = 0;
    }
}

static int init_lindsink(sample_mpp2livesink_ctx_t * p_ctx)
{
    lindsink_type_e type = -1;
#ifdef SINK_MODE_FF
    type = LINDSINK_FF;
#endif
#ifdef SINK_MODE_LIVE
    type = LINDSINK_LIVE;
#endif
    p_ctx->p_sink = lindsink_create(type, &p_ctx->sink_cfg);
    if (!p_ctx->p_sink)
    {
        return -1;
    }

    return 0;
}

static void deinit_lindsink(sample_mpp2livesink_ctx_t * p_ctx)
{
    if (p_ctx->p_sink != 0)
    {
        p_ctx->p_sink->close(p_ctx->p_sink);
        p_ctx->p_sink = 0;
    }
}

static int start_mpp_source(sample_mpp2livesink_ctx_t * p_ctx)
{
    int ret = 0;

    /**
     * start aenc
     */
    if (p_ctx->h_audio)
    {
        mpp_audio_aenc_set_params(p_ctx->h_audio, 0, &p_ctx->aenc_cfg);
        ret = mpp_audio_aenc_start(p_ctx->h_audio, 0);
        if (ret < 0)
        {
            loge("mpp_audio_aenc_start failed");
            return -1;
        }
    }

    if (p_ctx->h_camera)
    {
        /**
         * start preview
         */
        if (p_ctx->b_enable_preview)
        {
            mpp_cam_preview_set_params(p_ctx->h_camera, &p_ctx->preview_cfg);
            mpp_cam_preview_start2(p_ctx->h_camera);
        }

        /**
         * start venc
         */
        mpp_cam_venc_set_params(p_ctx->h_camera, 0, &p_ctx->venc_cfg);
        ret = mpp_cam_venc_start(p_ctx->h_camera, 0);
        if (ret < 0)
        {
            loge("mpp_cam_venc_start failed");
            return -1;
        }
    }

    return 0;
}

static void stop_mpp_source(sample_mpp2livesink_ctx_t * p_ctx)
{
    /**
     * stop streaming
     */
    if (p_ctx->h_audio)
    {
        mpp_audio_aenc_stop(p_ctx->h_audio, 0);
    }

    if (p_ctx->h_camera)
    {
        mpp_cam_venc_stop(p_ctx->h_camera, 0);
        if (p_ctx->b_enable_preview)
        {
            mpp_cam_preview_stop2(p_ctx->h_camera);
        }
    }
}

static void print_usage(const char * thiz)
{
    printf("\033[32m");
    printf("Usage: \n");
    printf("    %s <options>\n", thiz);
    printf("options can be this:\n");
    printf("    -i [ip]             : the ip address of rtsp server, default getting from the eth0 or wlan0. \n");
    printf("    -p [port]           : the port of rtsp server, default '8554'.\n");
    printf("    -n [name]           : the name rtsp server, default 'live'.\n");
    printf("    -s [w h]            : video capture size, default '1920 1080'\n");
    printf("    -f [fps]            : video capture and encoder framerate, default '30'\n");
    printf("    -vs [w h]           : video encoding size, default '1920 1080'\n");
    printf("    -vt [h264/h265]     : video encoding type, default 'h264'\n");
    printf("    -vb [bitrate]       : video encoding bitrate in unit bps, default 2*1024*1024bps\n");
    printf("    -disable_preview    : default enable preview\n");
    printf("    -dr [rotate]        : display rotate degree\n");
    printf("    -pos [x y w h]      : capture preview position on the screen, default full screen\n");
#ifdef SINK_MODE_FF
    printf("    -t [type]           : the sink type, can be 'mpegts/mp4/rtp/rtp_mpegts/rtsp/rtmp'\n");
    printf("such as: %s -t rtp_mpegts -i 192.168.3.118 -p 12345 \n", thiz);
    printf("         %s -t rtp -i 192.168.3.118 -p 12342 -n test \n", thiz);
    printf("         %s -t mp4 -n /tmp/out.mp4 \n", thiz);
    printf("         %s -t mpegts -n /tmp/out.ts \n", thiz);
#endif
#ifdef SINK_MODE_LIVE
    printf("The rtsp server url is: rtsp://[ip]:[port]/[name] \n");
    printf("You can also find the server url in the log. \n");
    printf("    such as: rtsp url: rtsp://192.168.3.168:8554/live \n");
    printf("Then, you can playback the url by streaming player such as 'vlc'. \n");
#endif
    printf("\033[0m\n");
}

#ifdef MELIS_OS
static int main_melis(int argc, char **argv)
#else
int main(int argc, char **argv)
#endif
{
    int ret = 0;
    sample_mpp2livesink_ctx_t sample_ctx;
    int i = 0;
    char any_ip[16] = "0.0.0.0";

    if ((argc == 2)
        && (!strcmp(argv[1], "-h")))
    {
        print_usage(argv[0]);
        return 0;
    }

    memset(&sample_ctx, 0, sizeof(sample_mpp2livesink_ctx_t));

    /*
     * default parameters
     * url: rtsp://ip:port/name
     */
    /*
     * capture parameters
     */
    sample_ctx.camera_cfg.msg_cb        = lindcamera_cb;
    sample_ctx.camera_cfg.user          = (void*)&sample_ctx;
    sample_ctx.camera_cfg.cap_width     = 1920;
    sample_ctx.camera_cfg.cap_height    = 1080;
    sample_ctx.camera_cfg.framerate     = 30;
    sample_ctx.camera_cfg.cap_width_sub = 1280;
    sample_ctx.camera_cfg.cap_height_sub = 720;
    sample_ctx.camera_cfg.flip          = 0;
    sample_ctx.camera_cfg.mirror        = 0;

    /*
     * preview parameters
     */
    sample_ctx.b_enable_preview         = 1;
    sample_ctx.preview_cfg.rotate       = ROTATE_DEGREE_0;
    sample_ctx.preview_cfg.pos.left     = 0;
    sample_ctx.preview_cfg.pos.top      = 0;
    sample_ctx.preview_cfg.pos.width    = 0;
    sample_ctx.preview_cfg.pos.height   = 0;

    /*
     * video encode parameters
     */
    sample_ctx.venc_cfg.venc_type   = AV_ENC_TYPE_H264;
    sample_ctx.venc_cfg.profile     = 1;
    sample_ctx.venc_cfg.venc_width  = 1920;
    sample_ctx.venc_cfg.venc_height = 1080;
    sample_ctx.venc_cfg.bitrate     = 2*1024*1024;
    sample_ctx.venc_cfg.framerate   = 30;    // same as the capture framerate
    sample_ctx.venc_cfg.gop         = 30;
    sample_ctx.venc_cfg.venc_rotate = 0;
    sample_ctx.venc_cfg.venc_mirror = 0;
    sample_ctx.venc_cfg.mux_type    = AV_MUX_TYPE_NONE;
    sample_ctx.venc_cfg.rc_mode     = 0;
    sample_ctx.venc_cfg.min_qp      = 5;
    sample_ctx.venc_cfg.max_qp      = 45;
    sample_ctx.venc_cfg.iframe_attach_spspps = 1;

    /*
     * audio caputre parameters
     */
    sample_ctx.audio_cfg.msg_cb     = lindaudio_cb;
    sample_ctx.audio_cfg.user       = (void*)&sample_ctx;
    sample_ctx.audio_cfg.channels   = 1;
    sample_ctx.audio_cfg.sample_rate = 16000;
    sample_ctx.audio_cfg.bits       = 16;

    /*
     * audio encode parameters
     */
    sample_ctx.aenc_cfg.aenc_type   = AV_ENC_TYPE_AAC;   // livesink now only support aac
    sample_ctx.aenc_cfg.mute        = 0;

    /*
     * sink parameters
     */
#ifdef SINK_MODE_LIVE
    sample_ctx.sink_cfg.ip          = any_ip;
    sample_ctx.sink_cfg.port        = 8554;
    sample_ctx.sink_cfg.name        = "live";

    sample_ctx.sink_cfg.have_video  = 1;
    sample_ctx.sink_cfg.venc_type   = sample_ctx.venc_cfg.venc_type;

    sample_ctx.sink_cfg.have_audio  = 1;
    sample_ctx.sink_cfg.aenc_type   = sample_ctx.aenc_cfg.aenc_type;
    sample_ctx.sink_cfg.channels    = sample_ctx.audio_cfg.channels;
    sample_ctx.sink_cfg.sample_rate = sample_ctx.audio_cfg.sample_rate;
#endif

#ifdef SINK_MODE_FF
    sample_ctx.sink_cfg.sink_type   = "rtp_mpegts";
    sample_ctx.sink_cfg.ip          = any_ip;
    sample_ctx.sink_cfg.port        = 8554;
    sample_ctx.sink_cfg.name        = "live";

    sample_ctx.sink_cfg.have_video  = 1;
    sample_ctx.sink_cfg.venc_type   = sample_ctx.venc_cfg.venc_type;
    sample_ctx.sink_cfg.width       = sample_ctx.venc_cfg.venc_width;
    sample_ctx.sink_cfg.height      = sample_ctx.venc_cfg.venc_height;
    sample_ctx.sink_cfg.framerate   = sample_ctx.venc_cfg.framerate;

    sample_ctx.sink_cfg.have_audio  = 1;
    sample_ctx.sink_cfg.aenc_type   = sample_ctx.aenc_cfg.aenc_type;
    sample_ctx.sink_cfg.channels    = sample_ctx.audio_cfg.channels;
    sample_ctx.sink_cfg.sample_rate = sample_ctx.audio_cfg.sample_rate;
    sample_ctx.sink_cfg.bits        = sample_ctx.audio_cfg.bits;
    sample_ctx.sink_cfg.period_size = 1024;
#endif

    for (i = 1; i < argc; )
    {
        if (!strcmp(argv[i], "-i"))
        {
            sample_ctx.sink_cfg.ip = argv[i + 1];
            i += 2;
        }
        else if (!strcmp(argv[i], "-p"))
        {
            sample_ctx.sink_cfg.port = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-n"))
        {
            sample_ctx.sink_cfg.name = argv[i + 1];
            i += 2;
        }
        else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "-cap_size"))
        {
            sample_ctx.camera_cfg.cap_width = atoi(argv[i + 1]);
            sample_ctx.camera_cfg.cap_height = atoi(argv[i + 2]);
            i += 3;
        }
        else if (!strcmp(argv[i], "-f"))
        {
            sample_ctx.camera_cfg.framerate= atoi(argv[i + 1]);
            sample_ctx.venc_cfg.framerate = atoi(argv[i + 1]);    // same as the capture framerate
            i += 2;
        }
        else if (!strcmp(argv[i], "-vs") || !strcmp(argv[i], "-enc_size"))
        {
            sample_ctx.venc_cfg.venc_width = atoi(argv[i + 1]);
            sample_ctx.venc_cfg.venc_height = atoi(argv[i + 2]);
            i += 3;
        }
        else if (!strcmp(argv[i], "-vt") || !strcmp(argv[i], "-enc_type"))
        {
            if (!strcmp(argv[i + 1], "h264"))
            {
                sample_ctx.venc_cfg.venc_type = AV_ENC_TYPE_H264;
            }
            else if (!strcmp(argv[i + 1], "h265"))
            {
                sample_ctx.venc_cfg.venc_type = AV_ENC_TYPE_H265;
            }
            else
            {
                // default use h264
                sample_ctx.venc_cfg.venc_type = AV_ENC_TYPE_H264;
            }
            sample_ctx.sink_cfg.venc_type = sample_ctx.venc_cfg.venc_type;
            i += 2;
        }
        else if (!strcmp(argv[i], "-vb") || !strcmp(argv[i], "-bitrate"))
        {
            sample_ctx.venc_cfg.bitrate = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-disable_preview"))
        {
            sample_ctx.b_enable_preview = 0;
            i++;
        }
        else if (!strcmp(argv[i], "-dr"))
        {
            int rotate = atoi(argv[i + 1]);
            switch(rotate)
            {
                case 0:
                    sample_ctx.preview_cfg.rotate = ROTATE_DEGREE_NONE;
                    break;
                case 90:
                    sample_ctx.preview_cfg.rotate = ROTATE_DEGREE_90;
                    break;
                case 180:
                    sample_ctx.preview_cfg.rotate = ROTATE_DEGREE_180;
                    break;
                case 270:
                    sample_ctx.preview_cfg.rotate = ROTATE_DEGREE_270;
                    break;
                default:
                    sample_ctx.preview_cfg.rotate = ROTATE_DEGREE_NONE;
                    logw("invalid rotate(%d), force to 0", rotate);
                    break;
            }
            i += 2;
        }
        else if (!strcmp(argv[i], "-pos"))
        {
            sample_ctx.preview_cfg.pos.left = atoi(argv[i + 1]);
            sample_ctx.preview_cfg.pos.top = atoi(argv[i + 2]);
            sample_ctx.preview_cfg.pos.width = atoi(argv[i + 3]);
            sample_ctx.preview_cfg.pos.height = atoi(argv[i + 4]);
            i += 5;
        }
#ifdef SINK_MODE_FF
        else if (!strcmp(argv[i], "-t"))
        {
            sample_ctx.sink_cfg.sink_type = argv[i + 1];
            i += 2;
        }
#endif
        else
        {
            i++;
        }
    }

#ifdef SINK_MODE_FF
    if (!strcmp(sample_ctx.sink_cfg.sink_type, "rtp"))
    {
        logd("rtp can only push one stream, now we disable the audio stream");
        sample_ctx.sink_cfg.have_audio = 0;
    }
#endif

    logd("video parameters, cap: %d x %d @ %d(fps), venc: %s, %d x %d, %d(bps), %s",
        sample_ctx.camera_cfg.cap_width,
        sample_ctx.camera_cfg.cap_height,
        sample_ctx.camera_cfg.framerate,
        (sample_ctx.venc_cfg.venc_type == AV_ENC_TYPE_H264) ? "h264" : "h265",
        sample_ctx.venc_cfg.venc_width,
        sample_ctx.venc_cfg.venc_height,
        sample_ctx.venc_cfg.bitrate,
        sample_ctx.b_enable_preview ? "enable preview" : "disable preview");

#if SAVE_FILE
    g_fd = fopen(g_filepath, "wb+");
    if (NULL == g_fd)
    {
        loge("Open %s file error", g_filepath);
        return _exit_;
    }
    g_fd_audio = fopen(g_filepath_audio, "wb+");
    if (NULL == g_fd_audio)
    {
        loge("Open %s file error", g_filepath_audio);
        return _exit_;
    }
#endif

    ret = init_lindsink(&sample_ctx);
    if (ret < 0)
    {
        loge("init_lindsink failed");
        goto _exit_;
    }

    ret = init_mpp_source(&sample_ctx);
    if (ret < 0)
    {
        loge("init_mpp_source failed");
        goto _exit1_;
    }

    ret = start_mpp_source(&sample_ctx);
    if (ret < 0)
    {
        loge("mpp_source_start failed");
        goto _exit2_;
    }

    while(1)
    {
        signed char key_val;
        int ret = wait_input_char(&key_val, 100000);
        if (ret < 0)
        {
            break;
        }
        else if (ret == 0)
        {
            continue;
        }

        if (key_val > 0 && key_val)
        {
            if (key_val == '\n')
            {
                printf("\n");
            }
            else
            {
                printf("********* key_val: %c ********** \n", key_val);
            }

            if (key_val == 'q' || key_val == 'Q')
            {
                break;
            }
        }
    }

    /**
     * stop streaming
     */
    stop_mpp_source(&sample_ctx);

_exit2_:
    deinit_mpp_source(&sample_ctx);

_exit1_:
    deinit_lindsink(&sample_ctx);

_exit_:

#if SAVE_FILE
    if (NULL != g_fd)
    {
        fclose(g_fd);
        g_fd = NULL;
    }
    if (NULL != g_fd_audio)
    {
        fclose(g_fd_audio);
        g_fd_audio = NULL;
    }
#endif

    logd("bye bye");

    return 0;
}

#ifdef MELIS_OS
FINSH_FUNCTION_EXPORT_ALIAS(main_melis, __cmd_sample_mppsrc_2_livesink, lindmpp sample);
#endif

